/**
 * 维护话术库
 *
 * 【名词定义】
 * - 这一整个功能，叫“话术库（words-library）”
 * - “话术库”模块维护了一系列“话术（words）”，话术的计量单位是“一套”
 * - 一套话术包含一系列“预定义消息内容（word）”
 * - 相同类型的“话术”可以放进同一个“话术分组（words-group）”，以便于查找和维护
 */
import { truthy, getLogger, Service } from '@gnlab/utils'
import omit from 'lodash/omit'
import pick from 'lodash/pick'
import { Subject } from 'rxjs'
import type { Services } from '@/services'
import { API } from '@/services/common/api'
import { WordType } from '@/services/common/definition'
import type { ChatRecord } from '@/services/contact-chat'
import { makeSendChatRecord, ChatRecordType } from '@/services/contact-chat'
import { formatRespWords, formatRespWordsGroup } from './definition'
import type { Words, Word, RespWords, WordsGroup, RespWordsGroup } from './definition'

export * from './definition'

const logger = getLogger('services/resources/words-library')

export class WordsLibrary extends Service<Services> {
  private get qywxs() {
    return this.services.qywxs
  }
  private get contacts() {
    return this.services.contacts
  }
  private get chat() {
    return this.services.chat
  }

  /**
   * 本地更新了话术时，此 observable 会推送一条通知
   */
  private readonly _updated = new Subject<void>()
  readonly updated = this._updated.asObservable()

  async load() {
    const resp = await API('/SpeechLibrary/GetSpeechLibrarys', {
      formatter(list: RespWords[]) {
        return list.map(formatRespWords).sort((a, b) => b.sort - a.sort || b.id - a.id)
      },
    })
    if (resp.success) {
      return resp.data
    } else {
      logger.warn('加载话术列表失败', resp.error)
      return []
    }
  }

  async addOrUpdate(words: Words | Omit<Words, 'id' | 'sort'>) {
    const resp = await API('/SpeechLibrary/SaveSpeechLibrary', {
      data: {
        id: 'id' in words ? words.id : undefined,
        type: words.isGlobal ? 0 : 1,
        groupId: words.groupId ?? 0,
        content: JSON.stringify(words.items),
      },
      formatter() {},
    })
    if (!resp.success) logger.warn(`话术更新失败：${resp.error}`)
    this._updated.next()
    return resp
  }

  async remove(id: number) {
    const resp = await API('/SpeechLibrary/RemoveSpeechLibrary', {
      data: { Id: id },
      formatter() {},
    })
    if (!resp.success) logger.warn(`话术删除失败：${resp.error}`)
    this._updated.next()
    return resp
  }

  /**
   * 对话术进行排序，把 fromId 的话术库移动到 toId 的位置
   * 若是从上往下挪，是挪到 toId 的后面；若是从下往上挪，是挪到 toId 的前面
   */
  async sort(fromId: number, toId: number) {
    const resp = await API('/SpeechLibrary/ChangeSpeechLibrarySort', {
      data: {
        currentId: fromId,
        anotherId: toId,
      },
      formatter() {},
    })
    if (!resp.success) logger.warn(`话术排序失败：${resp.error}`)
    this._updated.next()
    return resp
  }

  async loadGroups() {
    const resp = await API('/SpeechLibrary/GetSpeechLibraryGroups', {
      formatter(list: RespWordsGroup[]) {
        return list.map(formatRespWordsGroup)
      },
    })
    if (resp.success) {
      return resp.data
    } else {
      logger.warn('加载话术分组失败', resp.error)
      return []
    }
  }

  async addGroup(group: Omit<WordsGroup, 'id'>) {
    const resp = await API('/SpeechLibrary/SaveSpeechLibraryGroup', {
      data: {
        type: group.isGlobal ? 0 : 1,
        name: group.name,
      },
      formatter() {},
    })
    if (!resp.success) logger.warn(`话术分组创建失败：${resp.error}`)
    this._updated.next()
    return resp
  }

  async renameGroup(group: WordsGroup) {
    const resp = await API('/SpeechLibrary/SaveSpeechLibraryGroup', {
      data: {
        id: group.id,
        type: group.isGlobal ? 0 : 1,
        name: group.name,
      },
      formatter() {},
    })
    if (!resp.success) logger.warn(`话术分组保存失败：${resp.error}`)
    this._updated.next()
    return resp
  }

  async removeGroup(id: number) {
    const resp = await API('/SpeechLibrary/RemoveSpeechLibraryGroup', {
      data: {
        id,
      },
      formatter() {},
    })
    if (!resp.success) logger.warn(`话术分组删除失败：${resp.error}`)
    this._updated.next()
    return resp
  }

  // =========================
  // 与 Chat Service 联动
  // =========================

  /**
   * 将一条聊天内容添加为话术
   */
  async addFromChat(record: ChatRecord, isGlobal: boolean) {
    const words: Omit<Words, 'id' | 'sort'> = {
      isGlobal,
      groupId: null,
      items: [],
    }

    switch (record.type) {
      case ChatRecordType.Text:
        words.items.push({ type: WordType.Text, text: record.content.text })
        break
      case ChatRecordType.Image:
        words.items.push({ type: WordType.Images, images: [record.content.url] })
        break
      case ChatRecordType.Video:
        words.items.push({ type: WordType.Video, url: record.content.url })
        break
      case ChatRecordType.File:
        words.items.push({
          type: WordType.File,
          name: record.content.name,
          url: record.content.url,
        })
        break
      case ChatRecordType.LinkCard:
        words.items.push({
          type: WordType.LinkCard,
          image_url: record.content.image,
          ...pick(record.content, 'title', 'desc', 'url'),
        })
        break
      case ChatRecordType.MiniProgram:
        words.items.push({ type: WordType.MiniProgram, ...record.content })
        break
      case ChatRecordType.Videonum:
        words.items.push({ type: WordType.Videonum, ...record.content })
        break
      default:
        logger.warn(`不支持添加为话术的消息类型：${record.type}`, record)
        break
    }

    return this.addOrUpdate(words)
  }

  /**
   * 发送一组话术内容
   */
  async send(items: Word[], contactId?: string) {
    const contact = truthy(contactId)
      ? this.contacts.getById(contactId)
      : this.contacts.current.contact.value
    if (!contact) {
      logger.warn(
        truthy(contactId)
          ? `找不到联系人（id:${contactId}），话术内容发送失败。`
          : '没有当前联系人，话术内容发送失败',
      )
      return false
    }

    const records: ChatRecord[] = []
    for (const word of items) {
      switch (word.type) {
        case WordType.Text:
          records.push(makeSendChatRecord(ChatRecordType.Text, { atList: [], text: word.text }))
          break
        case WordType.Images:
          records.push(...word.images.map(url => makeSendChatRecord(ChatRecordType.Image, { url })))
          break
        case WordType.Video:
          records.push(makeSendChatRecord(ChatRecordType.Video, { url: word.url }))
          break
        case WordType.Audio:
          records.push(makeSendChatRecord(ChatRecordType.Audio, { url: word.url }))
          break
        case WordType.File:
          records.push(
            makeSendChatRecord(ChatRecordType.File, { name: word.name, url: word.url, size: 0 }),
          )
          break
        case WordType.LinkCard: {
          records.push(
            makeSendChatRecord(ChatRecordType.LinkCard, {
              title: word.title,
              desc: word.desc,
              image: word.image_url,
              url: word.url,
            }),
          )
          break
        }
        case WordType.PersonCard: {
          // 仅同一企业下的员工的名片能保证发送成功
          const currentQywx = this.qywxs.usables.value.get(contact.ownerWxid)
          if (currentQywx && currentQywx.corpid === word.corpid) {
            records.push(
              makeSendChatRecord(ChatRecordType.PersonCard, {
                avatar: word.avatar,
                nickname: word.nickname,
                userId: word.user_id,
              }),
            )
          } else {
            logger.info(
              `名片话术内容所属企业（corpid:${word.corpid}）与消息发送者的不匹配（corpid:${
                currentQywx?.corpid ?? ''
              }），无法发送名片话术内容`,
            )
          }
          break
        }
        case WordType.MiniProgram:
          records.push(makeSendChatRecord(ChatRecordType.MiniProgram, omit(word, 'type')))
          break
        case WordType.Videonum:
          records.push(makeSendChatRecord(ChatRecordType.Videonum, omit(word, 'type')))
          break
        case WordType.GroupInvite:
          logger.warn(`不支持发送此类话术：${word.type}`, word)
          break
        case WordType.GroupAt:
          logger.warn(`不支持发送此类话术：${word.type}`, word)
          break
      }
    }

    /* eslint-disable no-await-in-loop */
    for (const record of records) {
      await this.chat.send(record, contactId)
    }
    /* eslint-enable no-await-in-loop */

    return true
  }
}
